/* Send files to client */
AddCSLuaFile( "shared.lua" )
AddCSLuaFile( "cl_init.lua" )
AddCSLuaFile( "class.lua" )
AddCSLuaFile( "helpers/date_time.lua" )
AddCSLuaFile( "debug/debug.lua" )
AddCSLuaFile( "helpers/math_helper.lua" )
AddCSLuaFile( "table_extension.lua" )
AddCSLuaFile( "helpers/string_helper.lua" )
AddCSLuaFile( "helpers/color_helper.lua" )
AddCSLuaFile( "helpers/sanity_checks.lua" )
AddCSLuaFile( "debug/custom_hooks.lua" )
AddCSLuaFile( "timer.lua" )
AddCSLuaFile( "animation/keyframe.lua" )
AddCSLuaFile( "animation/animation.lua" )
AddCSLuaFile( "sql/classes.lua" )
AddCSLuaFile( "sql/delete_db.lua" )
AddCSLuaFile( "sql/create_db.lua" )
AddCSLuaFile( "sql/create_data.lua" )
AddCSLuaFile( "sql/load_data.lua" )
AddCSLuaFile( "sql/save_data.lua" )
AddCSLuaFile( "sql/fixed_data.lua" )
AddCSLuaFile( "teams.lua" )
AddCSLuaFile( "cl_scoreboard.lua" )
AddCSLuaFile( "team_extension.lua" )
AddCSLuaFile( "player_extension.lua" )
AddCSLuaFile( "vgui/fader.lua" )
AddCSLuaFile( "vgui/control.lua" )
AddCSLuaFile( "vgui/panel.lua" )
AddCSLuaFile( "vgui/frame.lua" )
AddCSLuaFile( "vgui/button.lua" )
AddCSLuaFile( "vgui/check_button.lua" )
AddCSLuaFile( "vgui/scrollbar.lua" )
AddCSLuaFile( "vgui/list_box.lua" )
AddCSLuaFile( "vgui/menu.lua" )
AddCSLuaFile( "vgui/menu_item.lua" )
AddCSLuaFile( "usermessages.lua" )
AddCSLuaFile( "concommands.lua" )
AddCSLuaFile( "datastreams.lua" )
AddCSLuaFile( "cl_datastreams.lua" )

/* Includes */
include( "shared.lua" )
include( "sql/delete_db.lua" )				-- WARNING: only when tables have changed and need to be rebuilt
include( "sql/create_db.lua" )
include( "sql/create_data.lua" )
include( "sql/load_data.lua" )
include( "sql/save_data.lua" )
include( "sql/fixed_data.lua" )				-- WARNING: only when tables have changed and need to be rebuilt
include( "datastreams.lua" )

/* ******************************************************************************************************************************** */
/* ********** TEAM HOOKS ********************************************************************************************************** */
/* ******************************************************************************************************************************** */

/* Initialization of the game mode */
function GM:Initialize()

	/* Debug output */
	DEBUG:Msg( "Initialize...", "GM.Initialize" )

	/* Do a DB test */
	TestDB()
	
	/* Create the actual DB if needed */
	self:CreateDataBase()
	
	/* Create data */
	if self.CreateData then
		self:CreateData()
	else
		DEBUG:Msg( "Create data function not found. Skipping.", "GM.Initialize" )
	end
	
	/* Create or load the teams info */
	LoadTeamsInfo()
end

/* Player spawn during game */
function GM:PlayerSpawn( ply )
	
	/* Debug output */
	DEBUG:Msg( ply:Nick() .. " (" .. ply:SteamID() .. "), " .. tostring(ply:Team()), "GM.PlayerSpawn" )
	
	/* If player is first spawning */
	if ply.FirstSpawn then
	
		/* Kill the player for spectating and reset his stats */
		GAMEMODE:PlayerSpawnAsSpectator( ply )
		ply:SetTeam( team.GetInfo( "Spectators" ).ID )
		ply:SetFrags(0)
		ply:SetDeaths(0)
		
		/* Spectate */
		ply:StripWeapons()
		ply:Spectate(OBS_MODE_FIXED)
	else
	
		/* Make sure he's ok and not spectating */
		ply:Extinguish()
		ply:UnSpectate()
	
		/* Output a debug message */
		local teamName = team.GetAllTeams()[ply:Team()].ChatName
		if string.match( teamName, "^[aeiouy].*$" ) then
			ply:ChatPrint( "You're now an " .. teamName .. "\n" )
		else
			ply:ChatPrint( "You're now a " .. teamName .. "\n" )
		end
	end
	
	/* Check if player info was retrieved correctly at first spawn */
	if ply.LastJoin and type( ply.LastJoin ) == "number" then
		DEBUG:Msg( ply:Nick() .. "'s player info is available", "GM.PlayerSpawn" )
	else
		DEBUG:Msg( ply:Nick() .. "'s player info is not available", "GM.PlayerSpawn" )
		if ply.QueryPlayerInfo and type( ply.QueryPlayerInfo ) == "function" then
			DEBUG:Msg( "Reload attempt: " .. tostring( ply:QueryPlayerInfo() ), "GM.PlayerSpawn" )
		end
	end
end

/* First player spawn */
function GM:PlayerInitialSpawn( ply )
	
	/* Remember he's only first spawning */
	ply.FirstSpawn = true
	
	/* Send the teams to the client for setup */
	for id,tm in pairs( team.GetGameTeams() ) do
	
		/* Debug output */
		DEBUG:Msg( "ID: " .. tostring( tm.ID ), "GM.PlayerInitialSpawn" )
		DEBUG:Msg( "Name: " .. tostring( tm.Name ), "GM.PlayerInitialSpawn" )
		DEBUG:Msg( "Color: " .. color.ToString( tm.Color ), "GM.PlayerInitialSpawn" )
		DEBUG:Msg( "Max percent: " .. tostring( tm.MaxPercent ), "GM.PlayerInitialSpawn" )
		DEBUG:Msg( "Chat name: " .. tostring( tm.ChatName ), "GM.PlayerInitialSpawn" )
		DEBUG:Msg( "Create time: " .. TimeToDateTimeString( tm.CreateTime ), "GM.PlayerInitialSpawn" )
		
		/* Send team info to client */
		DS.SendTeamInfo( ply, tm )
	end
	
	/* Debug output */
	DEBUG:Msg( ply:Nick() .. " (" .. ply:SteamID() .. "), " .. tostring(ply:Team()), "GM.PlayerInitialSpawn" )
	
	/* Load player info */
	local plyInfo = player.QueryPlayerInfo( ply )
	
	/* Error loading player info */
	if not plyInfo then
		
		/* Debug output */
		DEBUG:Msg( "Player info could not be retrieved nor created for '" .. ply:Nick() .. "'", "GM.PlayerInitialSpawn" )
		
	/* If ok, debug output everything */
	else
	
		/* Debug output */
		DEBUG:Msg( ply:Nick() .. "'s player info:", "GM.PlayerInitialSpawn" )
	
		/* Loop columns */
		for col,val in pairs( plyInfo ) do
		
			/* Debug output */
			DEBUG:Msg( " - " .. tostring( col ) .. ": " .. tostring( val ), "GM.PlayerInitialSpawn" )
		end
		
		/* Update last join */
		ply.LastJoin = os.time()
	end
end

/* Player died */
function GM:PlayerDeath( victim, inflictor, killer )

	/* Base stuff */
	self.BaseClass:PlayerDeath( victim, inflictor, killer )

	/* Set him as a prisonner */
	if not victim.FirstSpawn then
		SetPlayerTeam( victim, "Prisoners" )
	end
	
	/* Spectate */
	victim:StripWeapons()
	if killer and killer:IsValid() then
		victim:Spectate(OBS_MODE_DEATHCAM)
		victim:SpectateEntity(killer)
	else
		victim:Spectate(OBS_MODE_FIXED)
	end
	
	/* Update next spawn time */
	if not victim.FirstSpawn then
		victim.NextRespawn = CurTime() + 5
	end
	
	/* Debug output */
	DEBUG:Msg( victim:Nick() .. ", " .. tostring( team.GetInfo( victim:Team() ).ChatName ), "GM.PlayerSpawn" )
	
	return true
end

/* Player spawn required after death or round end */
function GM:PlayerSelectSpawn( ply )
	
	/* Debug output */
	DEBUG:Msg( ply:Nick() .. " (" .. ply:SteamID() .. "), " .. tostring(ply:Team()), "GM.PlayerSelectSpawn" )
	
	/* Spawn points */
	local spawns = nil
	
	/* Determine spawn class based on player's team */
	local spawnClass = nil
	if ply:Team() == team.GetInfo( "Attackers" ).ID then spawnClass = "info_player_attacker"
	elseif ply:Team() == team.GetInfo( "Defenders" ).ID then spawnClass = "info_player_defender"
	elseif ply:Team() == team.GetInfo( "Prisoners" ).ID then spawnClass = "info_player_prisoner"
	else spawnClass = "info_player_spectator" end
	
	/* If player is first spawning, choose a spectator spawn */
	if ply.FirstSpawn then spawnClass = "info_player_spectator" end
	
	/* Get the class associated spawns */
	spawns = ents.FindByClass( spawnClass )
	
	/* Debug output */
	if spawns and table.Count( spawns ) > 0 then
		DEBUG:Msg( tostring( table.Count( spawns ) ) .. " spawns of type '" .. tostring( spawnClass ) .. "' found", "GM.PlayerInitialSpawn" )
	else
		DEBUG:Msg( "No spawn of type '" .. tostring( spawnClass ) .. "' could be found. The default spawns will be used.", "GM.PlayerInitialSpawn" )
	end
	
	/* If no specific spawn could be found, use the default spawn points */
	if not spawns or table.Count( spawns ) == 0 then spawns = ents.FindByClass( "info_player_start" ) end
	
	/* Map should contain default spawns */
	if not spawns or table.Count( spawns ) == 0 then
		DEBUG:ErrMsg( "No default spawns found. Tell the map maker how to make a map !", "GM.PlayerSelectSpawn" )
		return nil
	end
	
	/* Select a random spawn and return it */
	local spawn = math.random(#spawns)
	DEBUG:Msg( "Selected spawn: " .. tostring( spawn ), "GM.PlayerSelectSpawn" )
	return spawns[ spawn ]
end

/* Every frame a player is dead */
function GM:PlayerDeathThink( ply )

	/* This is intended for players who are already playing only */
	if ply.FirstSpawn then
	
		/* Spawn them on demand */
		SpawnPlayerOnDemand( ply )
		return
	end

	/* Check if he can respawn */
	if ply.NextRespawn and ply.NextRespawn > CurTime() then return end
	
	/* Spawn him */
	DEBUG:Msg( "Auto-spawning player " .. ply:Nick(), "GM.PlayerDeathThink" )
	ply:Spawn()
end

/* Player receives weapons / ammo */
function GM:PlayerLoadout( ply )
	
	/* Debug output */
	DEBUG:Msg( ply:Nick() .. ", " .. tostring(ply:Team()), "GM.PlayerLoadout" )
	
	/* Strip weapons */
	ply:StripWeapons()
	
	/* If player doesn't have a loadout, use default loadout */
	if not ply.Loadout then
		DEBUG:Msg( "Not saved loadout for '" .. tostring( ply:Nick() ) .. "'. Using default.",  "GM.PlayerLoadout" )
		ply.Loadout = team.GetInfo( ply:Team() ).DefaultLoadout
	end
	
	/* Give current loadout */
	if ply.Loadout then
	
		/* Primary weapon */
		if ply.Loadout.Primary then
			DEBUG:Msg( "Giving primary '" .. tostring( ply.Loadout.Primary.Reference ) .. "' to " .. ply:Nick(), "GM.PlayerLoadout" )
			ply:Give( ply.Loadout.Primary.Reference )
		end
		
		/* Secondary weapon */
		if ply.Loadout.Secondary then
			DEBUG:Msg( "Giving secondary '" .. tostring( ply.Loadout.Secondary.Reference ) .. "' to " .. ply:Nick(), "GM.PlayerLoadout" )
			ply:Give( ply.Loadout.Secondary.Reference )
		end
		
		/* First inventory item */
		if ply.Loadout.Inventory1 then
			DEBUG:Msg( "Giving inventory1 '" .. tostring( ply.Loadout.Inventory1.Reference ) .. "' to " .. ply:Nick(), "GM.PlayerLoadout" )
			ply:Give( ply.Loadout.Inventory1.Reference )
		end
		
		/* Second inventory item */
		if ply.Loadout.Inventory2 then
			DEBUG:Msg( "Giving inventory2 '" .. tostring( ply.Loadout.Inventory2.Reference ) .. "' to " .. ply:Nick(), "GM.PlayerLoadout" )
			ply:Give( ply.Loadout.Inventory2.Reference )
		end
		
	/* Team loadout not found ??? */
	else
		DEBUG:ErrMsg( "Team loadout not found for team '" .. tostring( team.GetInfo( ply:Team() ).Name ) .. "'.",  "GM.PlayerLoadout" )
	end
end

/* Player is talking in chat */
function GM:PlayerSay( ply, text, public )

	/* Player is typing a command */
	if string.match( text, "^/.*$" ) then
		
		/* Get and send the command */
		local cmd = string.sub( text, 2 )
			
		/* Only allow logged players and log in command */
		if ply.Logged or string.match( cmd, "^bel_login" ) == "bel_login" then

			/* Send the command */
			DEBUG:Msg( ply:Nick() .. " is sending command '" .. cmd .. "'", "GM.PlayerSay" )
			ply:ConCommand( cmd )
		
			/* In that case, don't display the text in chat */
			return false
		end
	end
	
	/* Otherwise return the text entered */
	return text
end

/* Every frame of the gamemode */
function GM:Think()
	
	/* For all players */
	for k,ply in pairs( player.GetAll() ) do
		
		/* Spawn waiting players that requested it */
		if ply.FirstSpawn then
			
			/* Spawn player if required */
			SpawnPlayerOnDemand( ply )
		end
	end
end

/* Player uses noclip */
function GM:PlayerNoClip( ply )

	/* Disable noclip for everyone */
	return false
end

/* Player is switching flashlight on / off */
function GM:PlayerSwitchFlashlight( ply, on )

	/* Spectating players cannot use flashlight */
	local teamID = tonumber( team.GetInfo( ply:Team() ).ID )
	return ( teamID >= 1 and teamID <= 3 )
end

/* Player disconnected from server */
function GM:PlayerDisconnected( ply )

	/* Debug output */
	DEBUG:Msg( ply:Nick() .. " left the server. Last join: " .. tostring( ply.LastJoin ), "GM.PlayerDisconnected" )

	/* Save player info */
	SavePlayerInfo( ply )
end

/* Server is shutting down or changing map */
function GM:ShutDown()

	/* Debug output */
	DEBUG:Msg( "Server shutting down. Saving players stats...", "GM.ShutDown" )

	/* Do what should be done when a player disconnects */
	local allPlayers	= player.GetAll()
	local plyCount		= table.Count( allPlayers )
	if plyCount == 0 then
		DEBUG:Msg( "All players are already gone. Skipping.", "GM.ShutDown" )
	else
		for k,ply in pairs( allPlayers ) do
			self:PlayerDisconnected( ply )
		end
	end
	
	/* Save teams info (if changed) */
	SaveTeamsInfo()
end

/* An entity has been removed */
function GM:EntityRemoved( entity )
	
	/* Check if it is a player */
	if IsValid( entity ) and entity:IsPlayer() then
	
		/* Debug output */
		DEBUG:Msg( "Player removed: " .. entity:Nick(), "GM.EntityRemoved" )
		
		/* For players, do the disconnected stuff */
		self:PlayerDisconnected( entity )
	end
end

/* Open team menu */
function GM:ShowTeam( ply )
	SendUserMessage( "UMSG_ShowTeam", ply )
end